package com.catchman.utils;

import com.catchman.annotation.PermInfo;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.reflect.MethodUtils;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.context.ApplicationContext;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.util.ClassUtils;
import org.springframework.web.bind.annotation.RequestMapping;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * 采用内部定义的权限实体，主要目的是剥离util类和系统数据表的耦合性（外部调用后，重新映射即可）
 * 依赖于自定义的PermInfo 注解以及 swgger2 Api、ApiOperation注解，shiro RequiresPermissions注解（其他注解无效）
 */
public class SysPermUtil {

    /**
     * 解析获取注释权限，应该只扫描控制（"controller"）包内部的PermInfo
     *
     * @param context           当前运行上下文
     * @param controllerPackage 控制包路径
     * @return 权限list
     */
    public static List<PermVo> getAnnoPerm(ApplicationContext context, String controllerPackage) {
        Map<String, Object> mapController = context.getBeansWithAnnotation(PermInfo.class);
        Collection<Object> beans = mapController.values();
        List<PermVo> apiList = beans.stream().filter(b -> ClassUtils.getPackageName(b.getClass()).contains(controllerPackage))
                .map(bean -> {
                    Class<?> clz = bean.getClass();
                    PermVo moduleApiPerm = getModulePerm(clz);
                    List<PermVo> methodApiPerm = getApiPerm(clz, moduleApiPerm.getPval());
                    moduleApiPerm.getChildren().addAll(methodApiPerm);
                    return moduleApiPerm;
                }).collect(Collectors.toList());
        return apiList;
    }

    /**
     * 获取控制器中方法（method）上的注释，生成后台接口权限的信息
     *
     * @param clz
     * @return 系统权限实体集合
     */
    private static List<PermVo> getApiPerm(Class<?> clz, final String parentPval) {
        //获取clz类上有RequiresPermissions注解的所有方法
        List<Method> apiMethods = MethodUtils.getMethodsListWithAnnotation(clz.getSuperclass(), RequiresPermissions.class);
        return apiMethods.stream().map(method -> {

            //pname
            //获取method方法上的PermInfo注解的元数据
            PermInfo permInfo = AnnotationUtils.getAnnotation(method, PermInfo.class);
            String pnamePrimary = permInfo != null ? permInfo.value() : null;
            //pname备选1
            ApiOperation apiOperation = AnnotationUtils.getAnnotation(method, ApiOperation.class);
            String pnameSub0 = apiOperation != null ? apiOperation.value() : null;
            //pname备选2
            String pnameSub1 = method.getName();

            //pval值
            //获取method方法上的RequiresPermissions注解的元数据
            RequiresPermissions requiresPermissions = AnnotationUtils.getAnnotation(method, RequiresPermissions.class);

            PermVo perm = new PermVo();
            if (StringUtils.isNotBlank(pnamePrimary)) {
                perm.setPname(pnamePrimary);
            } else if (StringUtils.isNotBlank(pnameSub0)) {
                perm.setPname(pnameSub0);
            } else {
                perm.setPname(pnameSub1);
            }
            perm.setParent(parentPval);
            perm.setPval(requiresPermissions.value()[0]);
            return perm;

        }).collect(Collectors.toList());
    }

    /**
     * 获取控制器（class）的注释，生成后台接口模块权限的信息
     *
     * @param clz
     * @return 系统权限实体
     */
    private static PermVo getModulePerm(Class<?> clz) {
        PermVo perm = new PermVo();
        //首选值
        PermInfo permInfo = AnnotationUtils.getAnnotation(clz, PermInfo.class);
        if (permInfo == null) {
            //由于使用了RequiresPermissions注解的类在运行时会使用动态代理，即clz在运行时是一个动态代理，所以需要getSuperClass获取实际的类型
            permInfo = AnnotationUtils.getAnnotation(clz.getSuperclass(), PermInfo.class);
        }
        String pnamePrimary = null;
        String pvalPrimary = null;

        if (permInfo != null && permInfo.value() != null) {
            pnamePrimary = permInfo.value();
            pvalPrimary = permInfo.pval();
        }

        //权限名字、描述 备选1（swagger Api注释）
        Api api = AnnotationUtils.getAnnotation(clz, Api.class);
        String pnameSub0 = api != null ? api.value() : null;

        //权限名字、描述 备选2(类短名)
        String pnameSub1 = ClassUtils.getShortName(clz);

        //权限值，备选1（RequiresPermissions注释值）
        String pvalSub0 = null;
        RequiresPermissions rpAnno = AnnotationUtils.getAnnotation(clz, RequiresPermissions.class);
        if (rpAnno == null) {
            rpAnno = AnnotationUtils.getAnnotation(clz.getSuperclass(), RequiresPermissions.class);
        }
        if (rpAnno != null) {
            pvalSub0 = rpAnno.value()[0];
        }
        //权限值，备选2（RequestMapping注释值）
        RequestMapping requestMapping = AnnotationUtils.getAnnotation(clz, RequestMapping.class);
        if (requestMapping == null) {
            requestMapping = AnnotationUtils.getAnnotation(clz.getSuperclass(), RequestMapping.class);
        }
        String pvalSub1 = requestMapping.value()[0];

        //赋值权限描述、名字
        if (StringUtils.isNotBlank(pnamePrimary)) {
            perm.setPname(pnamePrimary);
        } else if (StringUtils.isNotBlank(pnameSub0)) {
            perm.setPname(pnameSub0);
        } else {
            perm.setPname(pnameSub1);
        }

        //赋值权限值
        if (StringUtils.isNotBlank(pvalPrimary)) {
            perm.setPval(pvalPrimary);
        } else if (StringUtils.isNotBlank(pvalSub0)) {
            perm.setPval(pvalSub0);
        } else {
            // 访问路径/替换为:,并在头部增加api指示即是访问权限 a:
            perm.setPval("" + pvalSub1.substring(1).replace("/", ":"));
        }
        return perm;
    }

    public static class PermVo {
        /**
         * 权限值
         */
        private String pval;
        /**
         * 权限描述或者名字
         */
        private String pname;
        /**
         * 权限parent节点
         */
        private String parent;
        /**
         * 权限子节点
         */
        private List<PermVo> children = new ArrayList<>();

        public String getPval() {
            return pval;
        }

        public void setPval(String pval) {
            this.pval = pval;
        }

        public String getPname() {
            return pname;
        }

        public void setPname(String pname) {
            this.pname = pname;
        }

        public String getParent() {
            return parent;
        }

        public void setParent(String parent) {
            this.parent = parent;
        }


        public List<PermVo> getChildren() {
            return children;
        }

        public void setChildren(List<PermVo> children) {
            this.children = children;
        }
    }

}
